# GF_PY3_SCRIPT_Accounting_First_In_First_Out.py
# Create by GF 2025-08-15 10:46

import pandas  # Pandas 2.0.3
# ..................................................
import GF_PY3_CLASS_STACK_FIFO_for_Accounting
# ..................................................
STACK_FIFO_for_Accounting = GF_PY3_CLASS_STACK_FIFO_for_Accounting.STACK_FIFO_for_Accounting

# ##################################################

def Accounting_First_In_First_Out(Pandas_DataFrame) -> dict:

    TEMP = Pandas_DataFrame.copy()
    # >>> print(TEMP)
    #    ROW_NUM  ID        时间  借方  贷方  借贷标志  发生额  发生额剩余数
    # 0        1   5  2022-12-31     0   200        贷    -200          -200
    # 1        2   6  2023-12-31     0   300        贷    -300          -300
    # 2        3   7  2024-06-30  1000     0        借    1000          1000
    # 3        4   8  2024-12-31     0   600        贷    -600          -600
    # 4        5   9  2025-04-30   300     0        借     300           300
    # 5        6  10  2025-05-31     0   400        贷    -400          -400
    # 6        7  11  2025-06-30   300     0        借     300           300
    # 7        8  12  2025-07-31   400     0        借     400           400
    # ..............................................
    Head_Symbol:str  = "资产" if (TEMP.loc[0, "借贷标志"] == "借") else "负债"  # 初始化首行借贷标志
    TAIL_ROWNUM:int  = TEMP["ROW_NUM"].max()  # 停止条件 (保险)
    List_DISUSE:list = []
    List_PROCES:list = []

    STACKAccounting = STACK_FIFO_for_Accounting()

    DEBUG_COUNTER = 30  # 防止陷入无限循环 (保险)
    while (TEMP.empty != True and 0 < DEBUG_COUNTER):

        if (Head_Symbol == "资产"):

            for i, ROW in TEMP.iterrows():

                if (ROW["借贷标志"] == "贷"):  # 与首行借贷标志相反 (开始消耗)

                    Occurrence_Remaining = STACKAccounting.Comsuming_for_IN(ID = ROW["ID"], Occurrence = ROW["发生额剩余数"] * (-1))

                    if (Occurrence_Remaining == 0):

                        TEMP.loc[i, "发生额剩余数"] = 0
                        List_PROCES.append(TEMP.loc[i, :].to_dict())
                        TEMP = TEMP[i < TEMP.index]  # 推进到下一行 (下次迭代从 "当前行 + 1" 开始)
                        break

                    if (Occurrence_Remaining != 0):  # 合同资产性质改变 (从 "资产" 转为 "负债")

                        TEMP.loc[i, "发生额剩余数"] = Occurrence_Remaining * (-1)
                        List_PROCES.append(TEMP.loc[i, :].to_dict())
                        TEMP = TEMP[i <= TEMP.index]  # 下次迭代从 "当前行" 开始)
                        Head_Symbol = "负债"
                        break

                if (ROW["借贷标志"] == "借"):

                    Record = {
                        "ROW_NUM":    ROW["ROW_NUM"],
                        "ID":         ROW["ID"],
                        "Direction":  "IN",
                        "Occurrence": ROW["发生额"],
                        "Remaining":  ROW["发生额剩余数"]
                    }
                    STACKAccounting.IN.append(Record)

                if (ROW["ROW_NUM"] == TAIL_ROWNUM):

                    TEMP = pandas.DataFrame()
                    break

        if (Head_Symbol == "负债"):

            for i, ROW in TEMP.iterrows():

                if (ROW["借贷标志"] == "借"):  # 与首行借贷标志相反 (开始消耗)

                    Occurrence_Remaining = STACKAccounting.Comsuming_for_OUT(ID = ROW["ID"], Occurrence = ROW["发生额剩余数"])

                    if (Occurrence_Remaining == 0):

                        TEMP.loc[i, "发生额剩余数"] = 0
                        List_PROCES.append(TEMP.loc[i, :].to_dict())
                        TEMP = TEMP[i < TEMP.index]  # 推进到下一行 (下次迭代从 "当前行 + 1" 开始)
                        break

                    if (Occurrence_Remaining != 0):  # 合同资产性质改变 (从 "负债" 转为 "资产")

                        TEMP.loc[i, "发生额剩余数"] = Occurrence_Remaining
                        List_PROCES.append(TEMP.loc[i, :].to_dict())
                        TEMP = TEMP[i <= TEMP.index]  # 下次迭代从 "当前行" 开始)
                        Head_Symbol = "资产"
                        break

                if (ROW["借贷标志"] == "贷"):

                    Record = {
                        "ROW_NUM":    ROW["ROW_NUM"],
                        "ID":         ROW["ID"],
                        "Direction":  "OUT",
                        "Occurrence": ROW["发生额"] * (-1),  # 转换为正数
                        "Remaining":  ROW["发生额剩余数"] * (-1)   # 转换为正数
                    }
                    STACKAccounting.OUT.append(Record)

                if (ROW["ROW_NUM"] == TAIL_ROWNUM):

                    TEMP = pandas.DataFrame()
                    break

        #DEBUG_COUNTER = DEBUG_COUNTER - 1

    RESULT = Pandas_DataFrame[["ROW_NUM", "ID", "时间", "借方", "贷方", "借贷标志", "发生额"]].copy()
    # ..............................................
    PROCES = pandas.DataFrame(List_PROCES)  # 过程中的发生额 "剩余数"
    # >>> print(PROCES)
    #    ROW_NUM  ID          时间  借方  贷方  借贷标志  发生额  发生额剩余数
    # 0        3   7    2024-06-30  1000     0        借    1000           500
    # 1        4   8    2024-12-31     0   600        贷    -600          -100
    # 2        5   9    2025-04-30   300     0        借     300           200
    # 3        6  10    2025-05-31     0   400        贷    -400          -200
    # 4        7  11    2025-06-30   300     0        借     300           100
    if (PROCES.empty != True):
        PROCES = PROCES[["ID", "发生额剩余数"]].groupby("ID", as_index = False).agg({"发生额剩余数": "last"})
        # >>> print(PROCES)
        #    ID  发生额剩余数
        # 0   7           500
        # 1   8          -100
        # 2   9           200
        # 3  10          -200
        # 4  11           100
    # ..............................................
    # ########## 提取 "栈 (Stack)" 队列: "尚处在队列中" (剩余数可能已经发生变化) ##########
    IN_OUT = pandas.concat([pandas.DataFrame(STACKAccounting.IN), pandas.DataFrame(STACKAccounting.OUT)])
    # >>> print(IN_OUT)
    #    ROW_NUM  ID  Direction  Occurrence  Remaining
    # 0        7  11         IN         300        100
    # 1        8  12         IN         400        400
    if (IN_OUT.empty != True):
        IN_OUT["Remaining"] = IN_OUT.apply(lambda ROW: ROW["Remaining"] * (-1) if (ROW["Direction"] == "OUT") else ROW["Remaining"], axis = 1)  # 添加符号
    # ..............................................
    # ########## 提取 "栈 (Stack)" 队列: "消耗数 / 剩余数" 最终明细 ##########
    DISUSE = pandas.DataFrame(STACKAccounting.DISUSE)
    # >>> print(DISUSE)
    #    ROW_NUM  ID  Direction  Occurrence  Remaining  Consumed  Consumer
    # 0        1   5        OUT         200          0       200         7
    # 1        2   6        OUT         300          0       300         7
    # 2        3   7         IN        1000          0       500         8
    # 3        4   8        OUT         600          0       100         9
    # 4        5   9         IN         300          0       200        10
    # 5        6  10        OUT         400          0       200        11
    if (DISUSE.empty != True):
        DISUSE["Consumed"] = DISUSE.apply(lambda ROW: ROW["Consumed"] * (-1) if (ROW["Direction"] == "OUT") else ROW["Consumed"], axis = 1)  # 添加符号
        # 数据聚合: "消耗数 -> 合计消耗数", "剩余数 -> 最后剩余数"
        DISUSE = DISUSE[["ID", "Consumed", "Remaining"]].groupby("ID", as_index = False).agg({"Consumed": "sum", "Remaining": "last"})
        # >>> print(DISUSE)
        #    ID  Consumed  Remaining
        # 0   5      -200          0
        # 1   6      -300          0
        # 2   7       500          0
        # 3   8      -100          0
        # 4   9       200          0
        # 5  10      -200          0
    # ..............................................
    RESULT["发生额剩余数"] = None
    # ..............................................
    if (PROCES.empty != True):
        for _ID_ in PROCES["ID"].drop_duplicates().values.tolist():
            RESULT_Index = RESULT[RESULT["ID"] == _ID_].index
            PROCES_Value = PROCES[PROCES["ID"] == _ID_]["发生额剩余数"].values[0]
            RESULT.loc[RESULT_Index, "发生额剩余数"] = PROCES_Value
    # ..............................................
    if (DISUSE.empty != True):
        for _ID_ in DISUSE["ID"].drop_duplicates().values.tolist():
            RESULT_Index = RESULT[RESULT["ID"] == _ID_].index
            DISUSE_Value = DISUSE[DISUSE["ID"] == _ID_]["Remaining"].values[0]
            RESULT.loc[RESULT_Index, "发生额剩余数"] = DISUSE_Value
    # ..............................................
    if (IN_OUT.empty != True):
        for _ID_ in IN_OUT["ID"].drop_duplicates().values.tolist():
            RESULT_Index = RESULT[RESULT["ID"] == _ID_].index
            IN_OUT_Value = IN_OUT[IN_OUT["ID"] == _ID_]["Remaining"].values[0]
            RESULT.loc[RESULT_Index, "发生额剩余数"] = IN_OUT_Value

    return {"剩余数": RESULT, "冲抵数": pandas.DataFrame(STACKAccounting.DISUSE)}

# #################### EXAMPLES ####################

# >>> import pandas
# >>>
# >>> RECORDS = [{"ROW_NUM": 1, "ID":  5, "时间": "2022-12-31", "借方":    0, "贷方": 200, "借贷标志": "贷", "发生额": -200},
# ...            {"ROW_NUM": 2, "ID":  6, "时间": "2023-12-31", "借方":    0, "贷方": 300, "借贷标志": "贷", "发生额": -300},
# ...            {"ROW_NUM": 3, "ID":  7, "时间": "2024-06-30", "借方": 1000, "贷方":   0, "借贷标志": "借", "发生额": 1000},
# ...            {"ROW_NUM": 4, "ID":  8, "时间": "2024-12-31", "借方":    0, "贷方": 600, "借贷标志": "贷", "发生额": -600},
# ...            {"ROW_NUM": 5, "ID":  9, "时间": "2025-04-30", "借方":  300, "贷方":   0, "借贷标志": "借", "发生额":  300},
# ...            {"ROW_NUM": 6, "ID": 10, "时间": "2025-05-31", "借方":    0, "贷方": 400, "借贷标志": "贷", "发生额": -400},
# ...            {"ROW_NUM": 7, "ID": 11, "时间": "2025-06-30", "借方":  300, "贷方":   0, "借贷标志": "借", "发生额":  300},
# ...            {"ROW_NUM": 8, "ID": 12, "时间": "2025-07-31", "借方":  400, "贷方":   0, "借贷标志": "借", "发生额":  400}]
# >>> df =  pandas.DataFrame(RECORDS)
# >>> print(df)
#    ROW_NUM  ID        时间  借方  贷方  借贷标志  发生额
# 0        1   5  2022-12-31     0   200        贷    -200
# 1        2   6  2023-12-31     0   300        贷    -300
# 2        3   7  2024-06-30  1000     0        借    1000
# 3        4   8  2024-12-31     0   600        贷    -600
# 4        5   9  2025-04-30   300     0        借     300
# 5        6  10  2025-05-31     0   400        贷    -400
# 6        7  11  2025-06-30   300     0        借     300
# 7        8  12  2025-07-31   400     0        借     400
# >>> df["发生额剩余数"] = df["发生额"]
# >>>
# >>> RESULT = Accounting_First_In_First_Out(df)
# >>> print(RESULT["剩余数"])
#    ROW_NUM  ID         时间  借方  贷方  借贷标志  发生额  发生额剩余数
# 0        1   5   2022-12-31     0   200        贷    -200             0
# 1        2   6   2023-12-31     0   300        贷    -300             0
# 2        3   7   2024-06-30  1000     0        借    1000             0
# 3        4   8   2024-12-31     0   600        贷    -600             0
# 4        5   9   2025-04-30   300     0        借     300             0
# 5        6  10   2025-05-31     0   400        贷    -400             0
# 6        7  11   2025-06-30   300     0        借     300           100
# 7        8  12   2025-07-31   400     0        借     400           400
# >>> print(RESULT["冲抵数"])
#    ROW_NUM  ID  Direction  Occurrence  Remaining  Consumed  Consumer
# 0        1   5        OUT         200          0       200         7
# 1        2   6        OUT         300          0       300         7
# 2        3   7         IN        1000          0       500         8
# 3        4   8        OUT         600          0       100         9
# 4        5   9         IN         300          0       200        10
# 5        6  10        OUT         400          0       200        11

# Signed by GF.
